POV-Ray : Newsgroups : povray.general : Interlocking Heightfields. : Re: Interlocking Heightfields. Server Time
31 Jul 2024 04:24:49 EDT (-0400)
  Re: Interlocking Heightfields.  
From: John VanSickle
Date: 12 May 2008 18:48:25
Message: <4828c939@news.povray.org>
Stephen wrote:
> Can anyone advise me on how to create a patchwork of heightfields
> where the edges match up? It is for a landscape and I've spent a
> couple of days trying to no avail.

I have something here that is horribly complex, but it may be helpful.

There are several macros here that do the work.  The upshot to all this 
is that I divide the x-z plane into a set of squares of unit size, and 
create a separate heightfield for each box.  Some of these squares are 
subdivided into smaller squares when they are near the camera.  Squares 
whose height_fields fall outside of the camera view are culled so that 
they don't take up memory space; this will be an issue if you have 
reflective surfaces that clearly show the off-camera portions of your 
scene.  The macro HF_Chunk() creates a height field for one square in 
the x-z plane, starting at a particular x-z location and going the 
specified size in the positive x and z directions.  I use one as the 
square size.  The precision of the height field is calculated based on 
the number of pixels the height field will span in the image.  This 
causes issues if the height field is near the viewpoint, so such height
fields are subdivided until every child is either off-camera (and 
culled) or no longer causing the problems from being too close.

The macro also allows you to pass a transform to it so that the height 
fields can be scaled and moved to where you want them.  It also requires 
a set of four vectors that determine the camera for you scene so that it 
can do the culling and precision calculations.

I have to warn the reader that this code is not particularly idiot-proof.

Hope this helps,
John

---------------------------

// This is used by the following macros.  It simply takes three
// vectors and uses them to create a transform.
#macro Matrix(mv_X,mv_Y,mv_Z,mv_L)
matrix <(mv_X).x,(mv_X).y,(mv_X).z,
         (mv_Y).x,(mv_Y).y,(mv_Y).z,
         (mv_Z).x,(mv_Z).y,(mv_Z).z,
         (mv_L).x,(mv_L).y,(mv_L).z >
#end

// This creates a single piece of pigment-based height field, which will
// tile neatly with other pieces using the same macro.  Imagine an array
// of boxes going forever in the x and z directions.
// * sU and sV give you a box in a particular column and row.
// * sS allows you to pick a larger or smaller chunk of height field. I
// recommend a value of 1.
// * pigUse is the pigment on which the height_fields are based.
// * transPut does a transform to the height fields so that they are
// scaled and placed where you want them.
// * vCamR is the right vector of the camera (not normalized)
// * vCamU is the up vector for the camera (not normalized)
// * vCamD is the direction vector for the camera (not normalized)
// * pCamL is the location of the camera
// * cLevel is the current subdivision level of the heightfield.
// This macro calls itself recursively, and cLevel is used to prevent
// infinite recursion.
// The recursion happens because the macro sets the precision of the
// heightfield to match the screen size of the heightfield (in pixels),
// which causes problems for heightfields that are near the camera.
// Dicing them up solved the problem.

#macro HF_Chunk(sU,sV,sS,pigUse,transPut,vCamR,vCamU,vCamD,pCamL,cLevel)
   #ifndef(Test) #local Test=1; #end
   // get a rough idea of the dimensions of the final block
   #local Block= height_field {
     function 32,32 { pigment { pigUse 
Matrix(x/sS,z,-y/sS,<-sU/sS,sV/sS+1,0>) } }
     scale <sS,1,sS>
     translate <sU,0,sV>
     transform transPut
   }
   #local pS=min_extent(Block);
   #local pE=max_extent(Block);
   #undef Block

   #if(BoxInProjectiveCamera(pCamL,vCamR,vCamU,vCamD,pS,pE))
// check to see if further subdivision is warranted
     #local fBefore=false;
     #local fBehind=false;
     #if( vdot(vnormalize(vCamD),<pS.x,pS.y,pS.z>-pCamL)>.01) #local 
fBefore=true; #else #local fBehind=true; #end
     #if( vdot(vnormalize(vCamD),<pE.x,pS.y,pS.z>-pCamL)>.01) #local 
fBefore=true; #else #local fBehind=true; #end
     #if( vdot(vnormalize(vCamD),<pS.x,pE.y,pS.z>-pCamL)>.01) #local 
fBefore=true; #else #local fBehind=true; #end
     #if( vdot(vnormalize(vCamD),<pE.x,pE.y,pS.z>-pCamL)>.01) #local 
fBefore=true; #else #local fBehind=true; #end
     #if( vdot(vnormalize(vCamD),<pS.x,pS.y,pE.z>-pCamL)>.01) #local 
fBefore=true; #else #local fBehind=true; #end
     #if( vdot(vnormalize(vCamD),<pE.x,pS.y,pE.z>-pCamL)>.01) #local 
fBefore=true; #else #local fBehind=true; #end
     #if( vdot(vnormalize(vCamD),<pS.x,pE.y,pE.z>-pCamL)>.01) #local 
fBefore=true; #else #local fBehind=true; #end
     #if( vdot(vnormalize(vCamD),<pE.x,pE.y,pE.z>-pCamL)>.01) #local 
fBefore=true; #else #local fBehind=true; #end

     #if( fBefore=true & fBehind=true & cLevel<8 )
       HF_Chunk(sU,     sV, 
sS/2,pigUse,transPut,vCamR,vCamU,vCamD,pCamL,cLevel+1)
       HF_Chunk(sU+sS/2,sV, 
sS/2,pigUse,transPut,vCamR,vCamU,vCamD,pCamL,cLevel+1)
       HF_Chunk(sU, 
sV+sS/2,sS/2,pigUse,transPut,vCamR,vCamU,vCamD,pCamL,cLevel+1)
 
HF_Chunk(sU+sS/2,sV+sS/2,sS/2,pigUse,transPut,vCamR,vCamU,vCamD,pCamL,cLevel+1)
     #else
       #local Detail=ProjectedBoxSize(pCamL,vCamR,vCamU,vCamD,pS,pE);
       #local Detail=max(2,min(512/(Test*7+1),Detail));
       height_field {
         function Detail,Detail { pigment { pigUse 
Matrix(x/sS,z,-y/sS,<-sU/sS,sV/sS+1,0>) } }
         scale <sS,1,sS>
         translate <sU,0,sV>
         transform transPut
       }
     #end // end of further subdivision check
   #end // end of entire container in projective camera test
#end // end of macro

// a macro to determine if a given box is in the camera view
// pCamL is the camera's location
// pCamD is the direction vector of the camera (*not* normalized)
// pCamR is the right vector of the camera (*not* normalized)
// pCamU is the up vector of the camera (*not* normalized)
// pStart is the box's starting location
// pEnd is the box's ending location
#macro BoxInProjectiveCamera(pCamL,vCamR,vCamU,vCamD,pS,pE)
   #local pQS=pS-pCamL;
   #local pQE=pE-pCamL;
   #local vLL=vCamD-vCamR/2-vCamU/2;
   #local vLR=vCamD+vCamR/2-vCamU/2;
   #local vUR=vCamD+vCamR/2+vCamU/2;
   #local vUL=vCamD-vCamR/2+vCamU/2;
   #local fResult=true;

   // if all points are behind the camera return false
   #if(max(
     vdot(<pQS.x,pQS.y,pQS.z>,vCamD),
     vdot(<pQS.x,pQS.y,pQE.z>,vCamD),
     vdot(<pQS.x,pQE.y,pQS.z>,vCamD),
     vdot(<pQS.x,pQE.y,pQE.z>,vCamD),
     vdot(<pQE.x,pQS.y,pQS.z>,vCamD),
     vdot(<pQE.x,pQS.y,pQE.z>,vCamD),
     vdot(<pQE.x,pQE.y,pQS.z>,vCamD),
     vdot(<pQE.x,pQE.y,pQE.z>,vCamD)
     )<0)
     #local fResult=false;
   #end

   // if all points are right of the view return false
   #local vN=vcross(vUR,vLR);
   #if(min(
     vdot(<pQS.x,pQS.y,pQS.z>,vN),
     vdot(<pQS.x,pQS.y,pQE.z>,vN),
     vdot(<pQS.x,pQE.y,pQS.z>,vN),
     vdot(<pQS.x,pQE.y,pQE.z>,vN),
     vdot(<pQE.x,pQS.y,pQS.z>,vN),
     vdot(<pQE.x,pQS.y,pQE.z>,vN),
     vdot(<pQE.x,pQE.y,pQS.z>,vN),
     vdot(<pQE.x,pQE.y,pQE.z>,vN)
     )>0)
     #local fResult=false;
   #end

   // if all points are below the view return false
   #local vN=vcross(vLR,vLL);
   #if(min(
     vdot(<pQS.x,pQS.y,pQS.z>,vN),
     vdot(<pQS.x,pQS.y,pQE.z>,vN),
     vdot(<pQS.x,pQE.y,pQS.z>,vN),
     vdot(<pQS.x,pQE.y,pQE.z>,vN),
     vdot(<pQE.x,pQS.y,pQS.z>,vN),
     vdot(<pQE.x,pQS.y,pQE.z>,vN),
     vdot(<pQE.x,pQE.y,pQS.z>,vN),
     vdot(<pQE.x,pQE.y,pQE.z>,vN)
     )>0)
     #local fResult=false;
   #end

   // if all points are left of the view return false
   #local vN=vcross(vLL,vUL);
   #if(min(
     vdot(<pQS.x,pQS.y,pQS.z>,vN),
     vdot(<pQS.x,pQS.y,pQE.z>,vN),
     vdot(<pQS.x,pQE.y,pQS.z>,vN),
     vdot(<pQS.x,pQE.y,pQE.z>,vN),
     vdot(<pQE.x,pQS.y,pQS.z>,vN),
     vdot(<pQE.x,pQS.y,pQE.z>,vN),
     vdot(<pQE.x,pQE.y,pQS.z>,vN),
     vdot(<pQE.x,pQE.y,pQE.z>,vN)
     )>0)
     #local fResult=false;
   #end

   // if all points are above the view return false
   #local vN=vcross(vUL,vUR);
   #if(min(
     vdot(<pQS.x,pQS.y,pQS.z>,vN),
     vdot(<pQS.x,pQS.y,pQE.z>,vN),
     vdot(<pQS.x,pQE.y,pQS.z>,vN),
     vdot(<pQS.x,pQE.y,pQE.z>,vN),
     vdot(<pQE.x,pQS.y,pQS.z>,vN),
     vdot(<pQE.x,pQS.y,pQE.z>,vN),
     vdot(<pQE.x,pQE.y,pQS.z>,vN),
     vdot(<pQE.x,pQE.y,pQE.z>,vN)
     )>0)
     #local fResult=false;
   #end

   (fResult)
#end

#macro ProjectedBoxSize(pL,vR,vU,vD,pS,pE)
   #local p000=vtransform(<pS.x,pS.y,pS.z>,transform { 
Matrix(vnormalize(vR),vnormalize(vU),vnormalize(vD),pL) inverse } );
   #local p001=vtransform(<pS.x,pS.y,pE.z>,transform { 
Matrix(vnormalize(vR),vnormalize(vU),vnormalize(vD),pL) inverse } );
   #local p010=vtransform(<pS.x,pE.y,pS.z>,transform { 
Matrix(vnormalize(vR),vnormalize(vU),vnormalize(vD),pL) inverse } );
   #local p011=vtransform(<pS.x,pE.y,pE.z>,transform { 
Matrix(vnormalize(vR),vnormalize(vU),vnormalize(vD),pL) inverse } );
   #local p100=vtransform(<pE.x,pS.y,pS.z>,transform { 
Matrix(vnormalize(vR),vnormalize(vU),vnormalize(vD),pL) inverse } );
   #local p101=vtransform(<pE.x,pS.y,pE.z>,transform { 
Matrix(vnormalize(vR),vnormalize(vU),vnormalize(vD),pL) inverse } );
   #local p110=vtransform(<pE.x,pE.y,pS.z>,transform { 
Matrix(vnormalize(vR),vnormalize(vU),vnormalize(vD),pL) inverse } );
   #local p111=vtransform(<pE.x,pE.y,pE.z>,transform { 
Matrix(vnormalize(vR),vnormalize(vU),vnormalize(vD),pL) inverse } );

   #local iRet=3;

   #if(p000.z>0 & p011.z>0)
     #local p000=p000/p000.z;
     #local p011=p011/p011.z;
     #local 
pQ=(p011-p000)*abs(vlength(vD))*<image_width/vlength(vR),image_height/vlength(vU),1>;
     #local iQ=floor(max(abs(pQ.x),abs(pQ.y)));
     #if(iQ>iRet) #local iRet=iQ; #end
   #end

   #if(p001.z>0 & p010.z>0)
     #local p001=p001/p001.z;
     #local p010=p010/p010.z;
     #local 
pQ=(p010-p001)*abs(vlength(vD))*<image_width/vlength(vR),image_height/vlength(vU),1>;
     #local iQ=floor(max(abs(pQ.x),abs(pQ.y)));
     #if(iQ>iRet) #local iRet=iQ; #end
   #end

   #if(p100.z>0 & p111.z>0)
     #local p100=p100/p100.z;
     #local p111=p111/p111.z;
     #local 
pQ=(p111-p100)*abs(vlength(vD))*<image_width/vlength(vR),image_height/vlength(vU),1>;
     #local iQ=floor(max(abs(pQ.x),abs(pQ.y)));
     #if(iQ>iRet) #local iRet=iQ; #end
   #end

   #if(p101.z>0 & p110.z>0)
     #local p101=p101/p101.z;
     #local p110=p110/p110.z;
     #local 
pQ=(p110-p101)*abs(vlength(vD))*<image_width/vlength(vR),image_height/vlength(vU),1>;
     #local iQ=floor(max(abs(pQ.x),abs(pQ.y)));
     #if(iQ>iRet) #local iRet=iQ; #end
   #end

   (iRet)
#end


Post a reply to this message

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.